﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using RWLibrary.Logic;
using RWLibrary.Logic.BooleanExpression;
using RWLibrary.Logic.Statements;

namespace RWLibrary.Parser
{
    public class StatementParser
    {
        private StatementToken token;
        private StatementScanner scanner;
        private World world;

        public StatementParser(World world)
        {
            this.world = world;
        }

        public void ParseStatements(string[] lines)
        {
            StatementManager manager = world.Statements;
            for (int i = 1; i <= lines.Length;i++)
            {
                Statement st;
                try
                {
                    st = ParseLine(lines[i - 1]);
                }
                catch (Exception ex)
                {
                    throw new ParserException(string.Format("There was a problem with parse at line: {0} \n{1}", i,
                                                            ex.Message));
                }
                
                if (st is InitiallyStatement)
                    manager.Initially = st as InitiallyStatement;
                else if (st is AlwaysStatement)
                    manager.Always.Add(st as AlwaysStatement);
                else if (st is CausesStatement)
                    manager.Causes.Add(st as CausesStatement);
                else if (st is ConstraintStatement)
                    manager.Constraint.Add(st as ConstraintStatement);
                else if (st is ObservationStatement)
                    manager.Observation.Add(st as ObservationStatement);
                else
                    manager.Release.Add(st as ReleaseStatement);
            }
            /*foreach (var line in world.Statements.Release.Select(r => r.ActionFamily.Name))
            {
                if(!world.Statements.Causes.Select(r => r.ActionFamily.Name).Contains(line))
                    throw new ParserException("Not all release statements have their causes pairs");
            }*/

            foreach (var line in world.Statements.Observation.SelectMany(r => r.ActionList))
            {
                if (!world.Statements.Causes.Select(r => r.ActionFamily.Name).Contains(line))
                    throw new ParserException("Not all actions in 'observation after' have been defined");
            }

            foreach (var line in world.Statements.Always.SelectMany(r => r.ActionList))
            {
                if (!world.Statements.Causes.Select(r => r.ActionFamily.Name).Contains(line))
                    throw new ParserException("Not all actions in 'always after' have been defined");
            }
            
        }

        public Statement ParseLine(string s)
        {
            scanner = new StatementScanner(s);

            token = scanner.Scan();
            switch (token.kind)
            {
                case StatementToken.Always:
                    {
                        IBooleanExpression alpha = Expression();
                        if (token.kind == StatementToken.Eof)
                            return new ConstraintStatement(alpha);
                        else
                            if (token.kind == StatementToken.After)
                            {
                                List<string> actionList = ActionList();
                                return new AlwaysStatement(alpha, actionList);
                            }
                        throw new ParserException(string.Format("End of line or after keyword expected expected at {0}", scanner.Pos));
                    }
                case StatementToken.Observable:
                    {
                        IBooleanExpression alpha = Expression();

                        if (token.kind == StatementToken.After)
                        {
                            List<string> actionList = ActionList();
                            return new ObservationStatement(alpha, actionList);
                        }
                        throw new ParserException(string.Format("After keyword expected expected at {0}", scanner.Pos));
                    }
                case StatementToken.Ident:
                    ActionFamily actionFamily = new ActionFamily { Name = token.val };
                    token = scanner.Scan();
                    if (token.kind == StatementToken.Causes)
                    {
                        IBooleanExpression alpha = Expression();
                        IBooleanExpression pi;
                        if (token.kind == StatementToken.If)
                            pi = Expression();
                        else
                            pi = new Truth();
                        int cost = 0;
                        if (token.kind == StatementToken.With)
                        {
                            token = scanner.Scan();
                            if (token.kind != StatementToken.Cost)
                                throw new ParserException(string.Format("Cost keyword expected expected at {0}", scanner.Pos));
                            token = scanner.Scan();
                            if (token.kind != StatementToken.Number)
                                throw new ParserException(string.Format("Cost value expected expected at {0}", scanner.Pos));
                            cost = int.Parse(token.val);
                        }
                        else
                            if (token.kind != StatementToken.Eof)
                                throw new ParserException(string.Format("With keyword expected expected at {0}", scanner.Pos));
                        world.AddAction(actionFamily);
                        return new CausesStatement(actionFamily, alpha, pi,  cost);
                    }

                    if (token.kind == StatementToken.Releases)
                    {
                        token = scanner.Scan();
                        if (token.kind != StatementToken.Ident)
                            throw new ParserException(string.Format("Fluent name expected at {0}", scanner.Pos));
                        string f = token.val;
                        token = scanner.Scan();
                        IBooleanExpression pi;
                        if (token.kind == StatementToken.If)
                            pi = Expression();
                        else
                            pi = new Truth();
                        int cost = 0;
                        if (token.kind == StatementToken.With)
                        {
                            token = scanner.Scan();
                            if (token.kind != StatementToken.Cost)
                                throw new ParserException(string.Format("Cost keyword expected expected at {0}",
                                                                        scanner.Pos));
                            token = scanner.Scan();
                            if (token.kind != StatementToken.Number)
                                throw new ParserException(string.Format("Cost value expected expected at {0}",
                                                                        scanner.Pos));
                            cost = int.Parse(token.val);
                        }
                        else if (token.kind != StatementToken.Eof)
                            throw new ParserException(string.Format("With keyword expected expected at {0}", scanner.Pos));

                        world.AddAction(actionFamily);
                        world.AddFluent(f);

                        return new ReleaseStatement(actionFamily, f, pi, cost);

                    }
                    break;
                case StatementToken.Initially:
                    {
                        IBooleanExpression alpha = Expression();
                        return new InitiallyStatement(alpha, new List<string>());
                    }
                case StatementToken.Impossible:
                    {
                        token = scanner.Scan();
                        if (token.kind != StatementToken.Ident)
                            throw new ParserException(string.Format("Action name expected at {0}", scanner.Pos));
                        string actionName = token.val;
                        token = scanner.Scan();
                        if (token.kind != StatementToken.If)
                            throw new ParserException(string.Format("If keyword expected expected at {0}",
                                                                    scanner.Pos));
                        IBooleanExpression alpha = Expression();
                        return new CausesStatement(new ActionFamily {Name = actionName}, new Falsity(),alpha, 0);
                    }
            }
            throw new ParserException(string.Format("Unexpected string at 0"));
            
        }

        private List<string> ActionList()
        {
            List<string> result = new List<string>(); 
            token = scanner.Scan();
            if(token.kind != StatementToken.Open_par)
                throw new ParserException(string.Format("Open parenthese for list of actions expected at {0}",scanner.Pos));
            token = scanner.Scan();
            while(token.kind == StatementToken.Ident)
            {
                result.Add(token.val);
                token = scanner.Scan();
            }
            if(token.kind != StatementToken.Close_par)
                throw new ParserException(string.Format("Close parenthese for list of actions expected at {0}", scanner.Pos));
            return result;
        }

        public IBooleanExpression ParseExpression(string s)
        {
            scanner = new StatementScanner(s);

            return Expression();
        }

        private IBooleanExpression Expression()
        {
            token = scanner.Scan();

            return Or();
        }
        private IBooleanExpression Or()
        {
            IBooleanExpression left = And();
            if (token.kind == StatementToken.Or)
            {
                token = scanner.Scan();
                return new Or(left, Or());
            }

            return left;
        }

        private IBooleanExpression Negation()
        {
            IBooleanExpression expression;
            if (token.kind == StatementToken.Negation)
            {
                token = scanner.Scan();
                expression = new Negation(Fluent());
            }
            else
                expression = Fluent();

            return expression;
        }
        private IBooleanExpression And()
        {
            IBooleanExpression left = Other();
            if (token.kind == StatementToken.And)
            {
                token = scanner.Scan();
                return new And(left, And());
            }

            return left;
        }

        private IBooleanExpression Other()
        {
            IBooleanExpression left = Negation();
            if (token.kind == StatementToken.Iif)
            {
                token = scanner.Scan();
                return new Iif(left, Negation());
            }
            if (token.kind == StatementToken.Implication)
            {
                token = scanner.Scan();
                return new Implication(left, Negation());
            }
            return left;
        }

        private IBooleanExpression Fluent()
        {
            IBooleanExpression expression;
            if (token.kind == StatementToken.Open_par)
            {
                expression = Expression();
                if (token.kind != StatementToken.Close_par)
                {
                    throw new ParserException(string.Format("Closing parenthese expected at {0}", scanner.Pos));
                }
            }
            else
                if (token.kind == StatementToken.Ident)
                {
                    world.AddFluent(token.val);
                    expression = new Fluent(token.val);
                }
                else
                    throw new ParserException(string.Format("Fluent name or open parenthese expected at {0}", scanner.Pos));
                    
            token = scanner.Scan();
            return expression;
        }

         

    }
}
